<script setup>
import { DeleteOutlined, EyeOutlined, PlusOutlined } from '@ant-design/icons-vue'
import { Form, message } from 'ant-design-vue'
import { filesize } from 'filesize'
import filesizeParser from 'filesize-parser'
import { findIndex, includes, some } from 'lodash-es'
import { nanoid } from 'nanoid'
import Sortable from 'sortablejs'
import { computed, onMounted, ref, watch } from 'vue'
import { deepMerge } from '@/utils'
import CropperDialog from '../Cropper/CropperDialog.vue'
import { Preview } from '../Preview'
import { STATUS_ENUM } from './config'

defineOptions({
  name: 'XUploadImage',
})
/**
 * 图片上传
 * @property {string | Array} modelValue v-model
 * @property {boolean} multiple 批量上传，默认：false
 * @property {number} width 宽，默认：120，单位：px
 * @property {number} height 高，默认：120，单位：px
 * @property {string} text 文案
 * @property {string | number} maxSize 最大限制，默认：2M
 * @property {string} accept 允许上传文件类型，默认：image/*
 * @property {boolean} disabled 禁用，默认：false
 * @property {boolean} round 圆角
 * @property {boolean} cropper 裁剪，仅支持单文件上传，默认：false，
 * @property {number} aspectRatio 比例，默认：自由裁剪
 * @property {number} quality 图片质量，取值范围：0-1，默认：1
 * @property {boolean} dragSort 拖拽排序，默认：false
 */
const props = defineProps({
  modelValue: {
    type: [String, Array],
    default: '',
  },
  multiple: {
    type: Boolean,
    default: false,
  },
  width: {
    type: Number,
    default: 120,
  },
  height: {
    type: Number,
    default: 120,
  },
  text: {
    type: String,
    default: '',
  },
  maxSize: {
    type: [String, Number],
    default: '2M',
  },
  accept: {
    type: String,
    default: 'image/*',
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  round: {
    type: Boolean,
    default: false,
  },
  cropper: {
    type: Boolean,
    default: false,
  },
  aspectRatio: {
    type: Number,
    default: 0,
  },
  quality: {
    type: Number,
    default: 1,
  },
  draggable: {
    type: Boolean,
    default: false,
  },
})

const emit = defineEmits(['update:modelValue', 'change'])

const { onFieldChange } = Form.useInjectFormItemContext()

const fileList = ref([])
const cropperDialogRef = ref()
const uploadImageRef = ref()
const sortable = ref(null)

const loading = computed(() => fileList.value.some(o => STATUS_ENUM.is('uploading', o.status)))
const showUploadBtn = computed(() => props.multiple || !fileList.value.length)
const dragSortDisabled = computed(() => !(props.draggable && !props.disabled))

watch(
  () => props.modelValue,
  () => {
    init()
  },
)

watch(
  () => dragSortDisabled.value,
  () => {
    initDragSort()
  },
)

onMounted(() => {
  init()
  initDragSort()
})

/**
 * 初始化
 */
function init() {
  const currentValue = props.modelValue
    ? Array.isArray(props.modelValue)
      ? props.modelValue
      : [props.modelValue]
    : []
  if (currentValue.length) {
    // 移除不存在的文件
    fileList.value.forEach((item, index) => {
      // 已完成 && 不存在与 modelValue 中
      if (STATUS_ENUM.is('done', item.status) && !includes(currentValue, item.src)) {
        fileList.value.splice(index, 1)
      }
    })
    // 添加不存在与 modelValue 中的文件
    currentValue.forEach((item) => {
      if (!some(fileList.value, { src: item })) {
        fileList.value.push(getItem({ src: item }))
      }
    })
  }
  else {
    fileList.value = []
  }
}

/**
 * 初始化拖拽排序
 */
function initDragSort() {
  if (sortable.value) {
    sortable.value.destroy()
    sortable.value = null
  }
  sortable.value = Sortable.create(uploadImageRef.value, {
    handle: '.j-upload-item',
    animation: 200,
    disabled: dragSortDisabled.value,
    onEnd: ({ newIndex, oldIndex }) => {
      const dragData = fileList.value.splice(oldIndex - 1, 1)[0]
      fileList.value.splice(newIndex - 1, 0, dragData)
      trigger()
    },
  })
}

/**
 * 预览
 * @param {*} record
 * @param {number} index 索引
 */
function handlePreview(record, index) {
  if (props.multiple) {
    // 多选
    Preview({
      urls: props.modelValue,
      current: index,
    })
  }
  else {
    // 单选
    Preview(record.src)
  }
}

/**
 * 移除
 * @param index
 */
function handleRemove(index) {
  fileList.value.splice(index, 1)
  trigger()
}

/**
 * 取消上传
 */
function handleCancel({ key }) {
  const index = fileList.value.findIndex(o => o.key === key)
  fileList.value.splice(index, 1)
}

/**
 * 上传前
 */
function onBeforeUpload(file) {
  const maxFileSize = typeof props.maxSize === 'number' ? props.maxSize : filesizeParser(props.maxSize)
  const checkFileSize = file?.size < maxFileSize
  if (!checkFileSize) {
    message.warning(`已忽略超过 ${filesize(maxFileSize)} 的文件`)
  }
  const checkCropper = props.cropper ? props.multiple : true
  if (props.cropper && !props.multiple) {
    const fileReader = new FileReader()
    fileReader.readAsDataURL(file)
    fileReader.onload = (e) => {
      cropperDialogRef.value?.open({
        src: e.target.result,
        quality: props.quality,
        aspectRatio: props.aspectRatio,
      })
    }
  }
  return checkFileSize && checkCropper
}

/**
 * 拖拽结束
 */
// function onDragEnd() {
//     trigger()
// }

/**
 * 自定义上传
 */
function customRequest(file) {
  const record = getItem({
    key: file.uid,
    src: URL.createObjectURL(file),
    status: STATUS_ENUM.getValue('wait'),
    percent: 0,
    file,
  })
  // 判断是否批量上传
  if (props.multiple) {
    // 批量上传
    fileList.value.push(record)
  }
  else {
    // 单文件上传
    fileList.value = [record]
  }
  if (!loading.value) {
    doUpload()
  }
}

/**
 * 执行上传
 */
async function doUpload() {
  // 判断是否还有待上传文件
  if (!some(fileList.value, { status: STATUS_ENUM.getValue('wait') })) {
    return
  }
  const index = findIndex(fileList.value, { status: STATUS_ENUM.getValue('wait') })
  const record = fileList.value[index]
  record.status = STATUS_ENUM.getValue('uploading')

  // 模拟示例
  record.percent = 100
  record.status = STATUS_ENUM.getValue('done')
  trigger()
  await doUpload()

  // 接口示例
  // const { code } = await apis.common.upload({
  //     file: record?.file,
  // })
  // if (config('http.code.success') === code) {
  //     // 上传进度
  //     record.percent = 100
  //     // 上传状态，根据实际情况更新
  //     record.status = STATUS_ENUM.getValue('done')
  //     // 更新文件 url
  //     record.src = data?.src
  //     // 触发事件
  //     trigger()
  //     // 递归上传，检查是否还有未上传的文件
  //     await doUpload()
  // }
}

/**
 * 基础结构
 * @return {{}}
 */
function getItem(obj) {
  return deepMerge(
    {
      key: nanoid(),
      src: '',
      status: STATUS_ENUM.getValue('done'),
      percent: 100,
    },
    obj,
  )
}

/**
 * 触发
 */
function trigger() {
  let value
  // 判断是否多选
  if (props.multiple) {
    // 多选
    value = fileList.value.filter(item => STATUS_ENUM.is('done', item.status)).map(item => item?.src ?? item)
  }
  else {
    // 单选
    value = (fileList.value.length ? fileList.value[0]?.src : fileList.value[0]) ?? ''
  }
  emit('update:modelValue', value)
  emit('change', value)
  onFieldChange()
}
</script>

<template>
  <div
    ref="uploadImageRef"
    :class="{
      'x-upload--round': round,
      'x-upload--disabled': disabled,
    }"
    class="x-upload x-upload-image"
  >
    <a-upload
      v-if="showUploadBtn"
      :accept="accept"
      :before-upload="onBeforeUpload"
      :custom-request="({ file }) => customRequest(file)"
      :disabled="disabled"
      :multiple="multiple"
      :show-upload-list="false"
    >
      <slot>
        <div
          :class="{
            'x-upload-btn--hover': !disabled,
          }"
          :style="{
            width: `${width}px`,
            height: `${height}px`,
          }"
          class="x-upload-btn"
        >
          <div class="x-upload-btn__icon">
            <slot name="icon">
              <plus-outlined />
            </slot>
          </div>
          <div
            v-if="text"
            class="x-upload-btn__txt"
          >
            <slot name="text">
              {{ text }}
            </slot>
          </div>
        </div>
      </slot>
    </a-upload>
    <div
      v-for="(item, index) in fileList"
      :key="item.key"
      :class="{
        'x-upload-item--error': STATUS_ENUM.is('error', item.status),
      }"
      :style="{
        width: `${width}px`,
        height: `${height}px`,
      }"
      class="x-upload-item j-upload-item"
    >
      <img
        :src="item.src"
        alt=""
      >
      <template v-if="['error', 'done'].includes(STATUS_ENUM.getKey(item.status))">
        <div class="x-upload-actions">
          <div
            v-if="STATUS_ENUM.is('done', item.status)"
            class="x-upload-action"
            @click="handlePreview(item, index)"
          >
            <eye-outlined />
          </div>
          <div
            v-if="!disabled"
            class="x-upload-action"
            @click="handleRemove(index)"
          >
            <delete-outlined />
          </div>
        </div>
      </template>
      <template v-else>
        <div class="x-upload-status">
          <template v-if="STATUS_ENUM.is('uploading', item.status)">
            <div>{{ item.percent }}%</div>
            <a-progress
              :percent="item.percent"
              :show-info="false"
              :stroke-width="4"
            />
          </template>
          <template v-if="STATUS_ENUM.is('wait', item.status)">
            <div>{{ STATUS_ENUM.getDesc(item.status) }}</div>
            <span
              class="x-upload-action"
              @click="handleCancel(item)"
            >
              取消上传
            </span>
          </template>
        </div>
      </template>
    </div>
  </div>

  <!-- 裁剪 -->
  <cropper-dialog
    v-if="cropper && !multiple"
    ref="cropperDialogRef"
    @ok="(_, { file }) => customRequest(file)"
  />
</template>

<style lang="less" scoped>
.x-upload {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;

  // 圆角
  &--round {
    .x-upload-btn,
    .x-upload-item {
      border-radius: 10em;
    }
  }

  // 禁用
  &--disabled {
    .x-upload-btn {
      opacity: 0.5;
      cursor: not-allowed;
    }
  }

  &-btn {
    border: @color-border dashed 1px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s;
    border-radius: @border-radius;

    &--hover {
      &:hover {
        border-color: @color-primary;
        color: @color-primary;
      }
    }

    &__icon {
      font-size: 20px;
    }

    &__txt {
      margin: 8px 0 0;
    }
  }

  &-item {
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    overflow: hidden;
    background: hsv(0, 0, 96%);
    border-radius: @border-radius;

    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    &:hover {
      .x-upload-actions {
        opacity: 1;
      }
    }

    &--error {
      &::before {
        position: absolute;
        content: '';
        width: 100%;
        height: 100%;
        border: @color-error dashed 1px;
        z-index: 2;
        pointer-events: none;
      }
    }
  }

  &-actions {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    background: rgba(0, 0, 0, 0.25);
    opacity: 0;
    transition: all 0.15s;
  }

  &-action {
    min-width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    border-radius: 2px;
    cursor: pointer;
    background: rgba(0, 0, 0, 0.25);
    transition: all 0.15s;
    font-size: 12px;
    padding: 0 4px;

    &:hover {
      background: rgba(0, 0, 0, 0.5);
    }
  }

  &-status {
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.25);
    padding: 0 16px;
    color: #fff;

    &--error {
      color: @color-error;
    }

    &--done {
      color: @color-success;
    }
  }
}
</style>
